# Threejs交互实现
实现一个场景：通过鼠标选取物体，在物体位置生成一个html元素
## raycaster(鼠标拾取物体）
### 如何通过鼠标拾取三维场景中的物体？
我们试想在一个黑暗的房间用手电筒找东西，我们手电筒射出的光线所触及的物体，就是我们视觉反馈我们所看到的物体。在threejs中也提供类似的Raycaster（光线投射）来充当手电筒。
### 如何使用光线投射？
我们通过光线投射拾取物体需要两个条件，一个是**手电筒在哪个位置进行照射**，一个是**手电筒朝什么方向进行照射**。
对应在threejs中便是`setFromCamera(coords: Vector2, camera: Camera)`函数：

- **coords**表示朝什么方向进行照射进行照射，一般是在标准化设备坐标中鼠标的二维坐标---X分量和Y分量应当在-1到1之间
- **camera **表示在哪个位置进行照射，射线所来源的摄像机
```javascript
// 射线对象
var raycaster = new THREE.Raycaster();
// 鼠标标准化二维坐标
var mouse = new THREE.Vector2();

function onMouseMove( event ) {

	// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)，此处为全屏操作，如果适应容器大小需使用renderer.domElement.getBoundingClientRect()获取渲染范围，进行相应计算

	mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
	mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

}

function render() {

	// 通过摄像机和鼠标位置更新射线
	raycaster.setFromCamera( mouse, camera );

	// 计算物体和射线的焦点
	var intersects = raycaster.intersectObjects( scene.children );

	for ( var i = 0; i < intersects.length; i++ ) {

		intersects[ i ].object.material.color.set( 0xff0000 );

	}

	renderer.render( scene, camera );

}

window.addEventListener( 'mousemove', onMouseMove, false );

window.requestAnimationFrame(render);
```
## 坐标转换
我们现在已经通过鼠标拾取到物体，但是如何在物体位置创建一个html元素。
### 世界坐标
顾名思义，世界坐标系就是用来描述整个3d场景的坐标系，它的原点就是(0, 0, 0)点。
### 屏幕坐标
由于我们获取的物体的位置坐标是一个**三维向量(Vector3)，**因此我们需要通过**Vector3**的**project（）**函数将三维的世界坐标，映射为屏幕标准化向量坐标，再通过计算得出div的绝对位置。
```jsx
// pos为交互的物体的position信息
const transMatrix = (pos) => {
    let world_vec = new Vector3(pos.x, pos.y, pos.z);
    let vec = world_vec.project(camera);
    let x = Math.round((vec.x + 1) * renderer.domElement.offsetWidth) / 2;
    let y = Math.round((1 - vec.y) * renderer.domElement.offsetHeight) / 2;
    return { x, y };
  };
```
# Threejs轨迹移动实现
## 思路
给需要移动的物体制定一条移动轨迹，让物体沿着所制定的移动轨迹进行移动
## CatmullRomCurve3
threejs文档介绍：CatmullRomCurve3使用Catmull-Rom算法从一系列点创建平滑的3d样条曲线
```javascript
let curve = new CatmullRomCurve3([
      // 起点
      new Vector3(-5, 1, 0),
      // 中间节点
      new Vector3(0, 1, 5),
      new Vector3(3, 1, 0),
      new Vector3(6, 1, -5),
      // 终点
      new Vector3(9, 1, 0),
    ]);
```
## 生成了轨迹之后，如何把它应用到物体移动中？

1. 通过轨迹自带方法getPointAt()获取轨迹上各个点的位置。
2. 再在每次渲染时，对物体的position依据获取到的点位置进行修改。
### getPointAt ( u : Float, optionalTarget : Vector ) : Vector
**u：**根据弧长在曲线上的位置。必须在[0, 1]范围内
**optionalTarget：**（可选）如果指定，结果将被复制到此Vector中，否则将创建一个新的Vector
```javascript
// pos为在弧线上的那个位置 0为起点 1为终点
let pos = 0;
const render = () => {
    requestAnimationFrame(render);
    if (curve && target) {
      if (pos < 1) {
        // 获取并设置目标物体的x和z坐标和轨迹上获取的点一致
        target.position.x = curve.getPointAt(pos).x;
        // target.position.x = curve.getPointAt(pos).x;
        target.position.z = curve.getPointAt(pos).z;
        pos += 0.001;
      } else {
        pos = 0;
      }
    }
    renderer.render(scene, camera);
  };
```
# Demo：
# Threejs交互实现

实现一个场景：通过鼠标选取物体，在物体位置生成一个html元素

## raycaster(鼠标拾取物体）

### 如何通过鼠标拾取三维场景中的物体？

我们试想在一个黑暗的房间用手电筒找东西，我们手电筒射出的光线所触及的物体，就是我们视觉反馈我们所看到的物体。在threejs中也提供类似的Raycaster（光线投射）来充当手电筒。

### 如何使用光线投射？

我们通过光线投射拾取物体需要两个条件，一个是**手电筒在哪个位置进行照射**，一个是**手电筒朝什么方向进行照射**。
对应在threejs中便是`setFromCamera(coords: Vector2, camera: Camera)`函数：

- **coords**表示朝什么方向进行照射进行照射，一般是在标准化设备坐标中鼠标的二维坐标---X分量和Y分量应当在-1到1之间
- **camera **表示在哪个位置进行照射，射线所来源的摄像机

```javascript
// 射线对象
var raycaster = new THREE.Raycaster();
// 鼠标标准化二维坐标
var mouse = new THREE.Vector2();

function onMouseMove( event ) {

	// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)，此处为全屏操作，如果适应容器大小需使用renderer.domElement.getBoundingClientRect()获取渲染范围，进行相应计算

	mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
	mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

}

function render() {

	// 通过摄像机和鼠标位置更新射线
	raycaster.setFromCamera( mouse, camera );

	// 计算物体和射线的焦点
	var intersects = raycaster.intersectObjects( scene.children );

	for ( var i = 0; i < intersects.length; i++ ) {

		intersects[ i ].object.material.color.set( 0xff0000 );

	}

	renderer.render( scene, camera );

}

window.addEventListener( 'mousemove', onMouseMove, false );

window.requestAnimationFrame(render);
```

## 坐标转换

我们现在已经通过鼠标拾取到物体，但是如何在物体位置创建一个html元素。

### 世界坐标

顾名思义，世界坐标系就是用来描述整个3d场景的坐标系，它的原点就是(0, 0, 0)点。

### 屏幕坐标

由于我们获取的物体的位置坐标是一个**三维向量(Vector3)，**因此我们需要通过**Vector3**的**project（）**函数将三维的世界坐标，映射为屏幕标准化向量坐标，再通过计算得出div的绝对位置。

```jsx
// pos为交互的物体的position信息
const transMatrix = (pos) => {
    let world_vec = new Vector3(pos.x, pos.y, pos.z);
    let vec = world_vec.project(camera);
    let x = Math.round((vec.x + 1) * renderer.domElement.offsetWidth) / 2;
    let y = Math.round((1 - vec.y) * renderer.domElement.offsetHeight) / 2;
    return { x, y };
  };
```

# Threejs轨迹移动实现

## 思路

给需要移动的物体制定一条移动轨迹，让物体沿着所制定的移动轨迹进行移动

## CatmullRomCurve3

threejs文档介绍：CatmullRomCurve3使用Catmull-Rom算法从一系列点创建平滑的3d样条曲线

```javascript
let curve = new CatmullRomCurve3([
      // 起点
      new Vector3(-5, 1, 0),
      // 中间节点
      new Vector3(0, 1, 5),
      new Vector3(3, 1, 0),
      new Vector3(6, 1, -5),
      // 终点
      new Vector3(9, 1, 0),
    ]);
```

## 生成了轨迹之后，如何把它应用到物体移动中？

1. 通过轨迹自带方法getPointAt()获取轨迹上各个点的位置。
2. 再在每次渲染时，对物体的position依据获取到的点位置进行修改。

### getPointAt ( u : Float, optionalTarget : Vector ) : Vector

**u：**根据弧长在曲线上的位置。必须在[0, 1]范围内
**optionalTarget：**（可选）如果指定，结果将被复制到此Vector中，否则将创建一个新的Vector

```javascript
// pos为在弧线上的那个位置 0为起点 1为终点
let pos = 0;
const render = () => {
    requestAnimationFrame(render);
    if (curve && target) {
      if (pos < 1) {
        // 获取并设置目标物体的x和z坐标和轨迹上获取的点一致
        target.position.x = curve.getPointAt(pos).x;
        // target.position.x = curve.getPointAt(pos).x;
        target.position.z = curve.getPointAt(pos).z;
        pos += 0.001;
      } else {
        pos = 0;
      }
    }
    renderer.render(scene, camera);
  };
```

# Demo
## [https://github.com/littlecow12138/threejs-interactive](https://github.com/littlecow12138/threejs-interactive)
